package org.cloudie.docs.editor.client.parts;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import org.cloudie.docs.editor.client.behaviors.HasReceiveBehavior;
import org.cloudie.docs.editor.client.commands.generic.FeedEntryCopyCommand;
import org.cloudie.docs.editor.client.commands.generic.SystemUploadFileCommand;
import org.cloudie.docs.editor.client.commands.generic.VirtualEntryCopyCommand;
import org.cloudie.docs.editor.client.commands.generic.VirtualEntryFolderRefreshCommand;
import org.cloudie.docs.editor.client.commands.generic.VirtualEntryMoveCommand;
import org.cloudie.docs.editor.client.content.contextmenus.DesktopContextMenu;
import org.cloudie.docs.editor.client.events.CommandEvent;
import org.cloudie.docs.editor.client.extended.File;
import org.cloudie.docs.editor.client.extended.FileDropEvent;
import org.cloudie.docs.editor.client.extended.FileDropHandler;
import org.cloudie.docs.editor.client.icons.explorer.document.DocumentExplorerIcon;
import org.cloudie.docs.editor.client.icons.explorer.virtual.VirtualExplorerIcon;
import org.cloudie.docs.editor.client.managers.ContextMenuManager;
import org.cloudie.docs.editor.client.managers.DropReceiveManager;
import org.cloudie.docs.editor.client.managers.WindowManager;
import org.cloudie.docs.editor.client.resources.icons.IconsHelper;
import org.cloudie.docs.editor.client.widgets.AdvancedPanel;
import org.cloudie.docs.editor.client.widgets.DesktopIcon;
import org.cloudie.docs.editor.client.widgets.ExplorerIcon;
import org.cloudie.docs.editor.client.widgets.Point;
import org.cloudie.docs.editor.client.widgets.Window;

import com.allen_sauer.gwt.dnd.client.PickupDragController;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.ContextMenuHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;

public class DesktopPart extends Composite
      implements ContextMenuHandler,
      HasReceiveBehavior {

  private AdvancedPanel boundaryPanel;
  private WindowManager windowManager;
  private ContextMenuManager contextMenuManager;
  private PickupDragController registeredDragController;
  private DropReceiveManager dropManager;
  private HandlerManager manager;
  private IconTray[] trays = new IconTray[0];
  private Style iconStyle;
  private int cellWidth = 90, cellHeight = 90, padding = 0;
  
  public DesktopPart() {
	boundaryPanel = new AdvancedPanel();
    boundaryPanel.setStylePrimaryName("cloudie-desktop");
    boundaryPanel.setSize("100%", "100%");
    boundaryPanel.addFileDropHandler(new FileDropHandler() {
		@Override
		public void onFileDrop(FileDropEvent event) {
		  event.getNativeEvent().preventDefault();
		  event.getNativeEvent().stopPropagation();
		  JsArray<File> files =
			  event.getNativeEvent().getDataTransfer().getFiles();
		  for (int i=0; i<files.length(); i++) {
		    CommandEvent.fire(new SystemUploadFileCommand(i, files.get(i)));
		  }
		}
    });
    initWidget(boundaryPanel);
    manager = new HandlerManager(this);
    dropManager = new DropReceiveManager(this);
    registeredDragController = new PickupDragController(boundaryPanel, true);
    registeredDragController.setBehaviorConstrainedToBoundaryPanel(true);
    registeredDragController.setBehaviorMultipleSelection(false);
    registeredDragController.setBehaviorDragStartSensitivity(1);
    registeredDragController.registerDropController(dropManager);
    windowManager = new WindowManager(boundaryPanel);
    contextMenuManager = new ContextMenuManager(boundaryPanel,
        DesktopContextMenu.get());
  }
  
  public void addTrays() {
    IconTray tray1 = new IconTray(0, IconsHelper.icons.TabWhite());
    boundaryPanel.add(tray1, 0, boundaryPanel.getOffsetHeight() - 10);
    trays = new IconTray[] { tray1 };
    tray1.setDragController(registeredDragController);
  }
  
  public void resize() {
    boundaryPanel.setWidgetPosition(trays[0], 0,
	    boundaryPanel.getOffsetHeight() - 100);
  }
  
  public void setStyle(String backgroundColor, String backgroundImage,
	  String backgroundPosition, String backgroundRepeat,
	  String iconLabelColor, String iconLabelBgColor) {
	this.getElement().getStyle().setBackgroundColor(backgroundColor);
	this.getElement().getStyle().setBackgroundImage("url(" + backgroundImage + ")");
	this.getElement().getStyle().setProperty("backgroundPosition", backgroundPosition);
	this.getElement().getStyle().setProperty("backgroundRepeat", backgroundRepeat);
	this.iconStyle = Style.createObject().cast();
	this.iconStyle.setColor(iconLabelColor);
	this.iconStyle.setBackgroundColor(iconLabelBgColor);
	for (int i=0; i<boundaryPanel.getWidgetCount(); i++) {
      Widget w = boundaryPanel.getWidget(i);
      if (w instanceof DesktopIcon) {
        DesktopIcon di = (DesktopIcon) w;
        di.setStyle(iconLabelColor, iconLabelBgColor);
      }
    }
	for (IconTray tray : trays) {
	  for (int i=0; i<tray.strip.getWidgetCount(); i++) {
		Widget w = tray.strip.getWidget(i);
        if (w instanceof DesktopIcon) {
          DesktopIcon di = (DesktopIcon) w;
          di.setStyle(iconLabelColor, iconLabelBgColor);
        }
	  }
	}
  }
  
  private Point getGridPosition(int x, int y) {
	Point pt = getActualPosition(x, y);
	x = pt.getX();
	y = pt.getY();
	x -= padding;
	y -= padding;
	if (x < 0) {
	  x = 0;
	}
	if (y < 0) {
	  y = 0;
	}
    int finalX = ((int)Math.round((double)x/cellWidth)*cellWidth);
    int finalY = ((int)Math.round((double)y/cellHeight)*cellHeight);
    return new Point(padding + finalX, padding + finalY);
  }
  
  private Point getActualPosition(int x, int y) {
	if (x < 0) {
	  x = com.google.gwt.user.client.Window.getClientWidth() + x;
	}
	if (y < 0) {
	  y = com.google.gwt.user.client.Window.getClientHeight() + y;
	}
	return new Point(x, y);
  }
  
  private Point getAvailablePosition(int x, int y) {
	ArrayList<Point> taken = new ArrayList<Point>();
    for (int i=0; i<boundaryPanel.getWidgetCount(); i++) {
      Widget w = boundaryPanel.getWidget(i);
      if (w instanceof DesktopIcon) {
        DesktopIcon di = (DesktopIcon) w;
        int dix = (boundaryPanel.getWidgetLeft(di)/cellWidth) - padding;
        int diy = (boundaryPanel.getWidgetTop(di)/cellHeight) - padding;
        taken.add(new Point(dix, diy));
      }
    }
    Collections.sort(taken, new Comparator<Point>() {
		@Override
		public int compare(Point p1, Point p2) {
		  if (p1.getY() < p2.getY()) return -1;
		  else if (p2.getY() < p1.getY()) return 1;
		  else {
			if (p1.getX() < p2.getX()) return -1;
		    else if (p2.getX() < p1.getX()) return 1;
		    else return 0;
		  }
		}
    });
    x = (x/cellWidth) - padding;
    y = (y/cellHeight) - padding;
    int xmax = (com.google.gwt.user.client.Window.getClientWidth() - (2*padding))/cellWidth;
    int curX = 0, curY = 0;
    for (Point pt : taken) {
      if (pt.getX() != curX || pt.getY() != curY) {
    	x = padding + (curX * cellWidth);
    	y = padding + (curY * cellHeight);
    	return new Point(x, y);
      } else {
    	if (curX < xmax) {
    	  curX++;
    	} else if (curX < xmax) {
    	  curX = 1;
    	  curY++;
    	}
      }
    }
    return new Point(0, 0);
  }

  public void refresh() {
    CommandEvent.fire(new VirtualEntryFolderRefreshCommand(null));
  }
  
  public void addIcon(DesktopIcon icon, int left, int top, int tray,
	    boolean distribute) {
	Point pt = getGridPosition(left, top);
	if (distribute) {
	  pt = getAvailablePosition(pt.getX(), pt.getY());
	}
	if (tray >= 0 && tray < trays.length) {
	  trays[tray].add(icon);
	} else {
      boundaryPanel.add(icon, pt.getX(), pt.getY());
	}
    icon.addContextMenuHandler(contextMenuManager);
    icon.setDragController(registeredDragController);
    if (this.iconStyle != null) {
      icon.setStyle(this.iconStyle.getColor(), this.iconStyle.getBackgroundColor());
    }
    registeredDragController.makeDraggable(icon);
  }
  
  public void removeIcon(String id) {
    DesktopIcon icon = getIcon(id);
    if (icon != null) {
      registeredDragController.unregisterDropController(icon.getDropReceiveManager());
      icon.removeFromParent();
    }
  }
  
  public DesktopIcon getIcon(String id) {
    for (int i=0; i<boundaryPanel.getWidgetCount(); i++) {
      Widget w = boundaryPanel.getWidget(i);
      if (w instanceof DesktopIcon) {
        DesktopIcon di = (DesktopIcon) w;
        if (di.getId().equals(id)) {
          return di;
        }
      }
    }
    for (int i=0; i<trays.length; i++) {
      IconTray tray = trays[i];
      DesktopIcon di = tray.getIcon(id);
      if (di != null) {
    	return di;
      }
    }
    return null;
  }
  
  public void addWindow(Window window, int left, int top) {
	Point pt = getActualPosition(left, top);
    windowManager.getWindowController().makeResizable(window);
    window.setDragController(registeredDragController);
    boundaryPanel.add(window, pt.getX(), pt.getY());
  }
  
  public void removeWindow(String id) {
	Window win = getWindow(id);
	if (win != null) {
	  registeredDragController.unregisterDropController(win.getDropReceiveManager());
	  boundaryPanel.remove(win);
	}
  }
  
  public Window getWindow(String id) {
    for (int i=0; i<boundaryPanel.getWidgetCount(); i++) {
      Widget w = boundaryPanel.getWidget(i);
      if (w instanceof Window) {
        Window win = (Window) w;
        if (win.getId() != null && win.getId().equals(id)) {
          return win;
        }
      }
    }
    return null;
  }
  
  public AbsolutePanel getBoundaryPanel() {
    return boundaryPanel;
  }
	
  @Override
  public void fireEvent(GwtEvent<?> event) {
    manager.fireEvent(event);
  }

  @Override
  public void onContextMenu(ContextMenuEvent event) {
	//pass through event, leave source intact
	Type<ContextMenuHandler> type = ContextMenuEvent.getType();
	for (int i=0; i<manager.getHandlerCount(type); i++) {
	  manager.getHandler(type, i).onContextMenu(event);
	}
  }

  @Override
  public boolean acceptsReceives(Widget dropped, boolean isFinal) {
    if (dropped instanceof ExplorerIcon ||
    	dropped instanceof DesktopIcon) {
      return true;
    }
	return false;
  }

  @Override
  public boolean acceptsReceives() {
	return true;
  }

  @Override
  public DropReceiveManager getDropReceiveManager() {
	return dropManager;
  }

  @Override
  public void onReceive(Widget received, int x, int y) {
    onReceive(received, x, y, -1);
  }
  
  protected void onReceive(Widget received, int x, int y, int tray) {
	if (received instanceof VirtualExplorerIcon) {
      VirtualExplorerIcon ei = (VirtualExplorerIcon) received;
	  if (ei.isProxy()) {
        CommandEvent.fire(new VirtualEntryCopyCommand(ei.getId(), null, x, y, tray));
	  } else {
        CommandEvent.fire(new VirtualEntryMoveCommand(ei.getId(), null, x, y, tray));
	  }
	  refresh();
	} else if (received instanceof DocumentExplorerIcon) {
      DocumentExplorerIcon fi = (DocumentExplorerIcon) received;
	  CommandEvent.fire(new FeedEntryCopyCommand(fi.getId(), null, x, y));
	} else if (received instanceof DesktopIcon) {
      DesktopIcon icon = (DesktopIcon) received;
      addIcon(icon, x, y, tray, false);
      CommandEvent.fire(new VirtualEntryMoveCommand(icon.getId(), null, x, y, tray));
  	}
  }

  @Override
  public void setDragController(PickupDragController controller) {
  }
  
  @Override
  public PickupDragController getDragController() {
    return registeredDragController;
  }
  
  public class IconTray extends Composite implements HasReceiveBehavior {

    protected HorizontalPanel panel;
    protected FlowPanel strip;
    protected DropReceiveManager dropManager;
    protected PickupDragController registeredDragController;
    protected int number;
    protected boolean collapsed = false;
  
    public IconTray(int number, 
    		AbstractImagePrototype image) {
      this.number = number;
	  this.strip = new FlowPanel();
	  this.strip.setStylePrimaryName("cloudie-tray-strip");
	  Image tab = image.createImage();
	  tab.setTitle("Drop items here to give them visibility.");
	  tab.setStylePrimaryName("cloudie-tray-tab");
	  tab.addClickHandler(new ClickHandler() {
		@Override
		public void onClick(ClickEvent event) {
		  toggleCollapse();
		}
	  });
	  this.panel = new HorizontalPanel();
	  this.panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
	  this.panel.setBorderWidth(0);
	  this.panel.setSpacing(0);
      this.panel.setStylePrimaryName("cloudie-tray");
	  this.panel.add(strip);
	  this.panel.add(tab);
	  initWidget(panel);
	  this.getElement().getStyle().setZIndex(4);
      this.dropManager = new DropReceiveManager(this);
    }
    
    public void toggleCollapse() {
	  collapsed = !collapsed;
	  strip.setVisible(!collapsed);
    }
  
    public void add(DesktopIcon w) {
	  this.strip.add(w);
	  collapsed = false;
	  strip.setVisible(true);
    }
  
    public DesktopIcon getIcon(String id) {
	  for (int i=0; i<strip.getWidgetCount(); i++){
	    Widget w = strip.getWidget(i);
	    if (w instanceof DesktopIcon) {
		  DesktopIcon icon = (DesktopIcon) w;
		  if (icon.getId().equals(id)) {
		    return icon;
		  }
	    }
	  }
	  return null;
    }

    @Override
    public boolean acceptsReceives(Widget dropped, boolean isFinal) {
	  return DesktopPart.this.acceptsReceives(dropped, isFinal);
    }

    @Override
    public boolean acceptsReceives() {
      return DesktopPart.this.acceptsReceives();
    }

    @Override
    public PickupDragController getDragController() {
	  return null;
    }

    @Override
    public DropReceiveManager getDropReceiveManager() {
      return dropManager;
    }

    @Override
    public void onReceive(Widget received, int x, int y) {
      DesktopPart.this.onReceive(received, x, y, number);
    }

    @Override
    public void setDragController(PickupDragController controller) {
      controller.registerDropController(dropManager);
      registeredDragController = controller;
    }
  
  }
  
}